package cmd

import (
	"errors"
	"fmt"
	"gorm.io/driver/clickhouse"
	"gorm.io/driver/mysql"
	"gorm.io/driver/postgres"
	"gorm.io/driver/sqlite"
	"gorm.io/driver/sqlserver"
	"log"
	"os"

	"gitee.com/ysfzf/zerox/cmd/common"

	"github.com/spf13/cobra"
	"gorm.io/gen"
	"gorm.io/gen/field"
	"gorm.io/gorm"
)

var gormCmd = &cobra.Command{
	Use:   "gorm",
	Short: "生成gorm代码",
	Run: func(cmd *cobra.Command, args []string) {
		f, _ := cmd.Flags().GetString("config")
		generateGorm(f)
	},
}

func init() {
	gormCmd.Flags().StringP("config", "c", "gorm.yaml", "The default configuration file is grom.yaml")

	rootCmd.AddCommand(gormCmd)
}

func generateGorm(configFile string) {
	var cfg common.GormConfig
	err := common.LoadConfig(configFile, &cfg)
	if err != nil {
		log.Fatal(err)
	}

	gdb, err := conndb()
	if nil != err {
		log.Fatal(err)
	}

	g := gen.NewGenerator(gen.Config{
		OutPath:           cfg.OutPath,
		ModelPkgPath:      cfg.ModelPkgName,
		WithUnitTest:      cfg.WithUnitTest,
		FieldNullable:     cfg.FieldNullable,
		FieldWithIndexTag: cfg.FieldWithIndexTag,
		FieldWithTypeTag:  cfg.FieldWithTypeTag,
		FieldCoverable:    cfg.FieldCoverable,
		Mode:              gen.WithDefaultQuery | gen.WithQueryInterface,
	})

	g.UseDB(gdb)
	dataMap := map[string]func(gorm.ColumnType) string{
		"int":       func(gorm.ColumnType) string { return "int64" },
		"smallint":  func(gorm.ColumnType) string { return "int64" },
		"mediumint": func(gorm.ColumnType) string { return "int64" },
		"integer":   func(gorm.ColumnType) string { return "int64" },
		"bigint":    func(gorm.ColumnType) string { return "int64" },
		"tinyint":   func(gorm.ColumnType) string { return "int8" },
		//"date":      func(gorm.ColumnType) string { return "string" },
		//"time":      func(gorm.ColumnType) string { return "string" },
		//"datetime":  func(gorm.ColumnType) string { return "string" },
	}

	g.WithDataTypeMap(dataMap)

	models := make([]interface{}, len(cfg.Tables))

	for index, tab := range cfg.Tables {

		opts := []gen.ModelOpt{
			//gen.FieldType("id", "int64"),
			gen.FieldType("deleted_at", "gorm.DeletedAt"),
		}
		for _, relate := range tab.Relates {

			if relate.Column == "" {
				log.Fatal("unkonw relate column")
			}

			if relate.Table == "" {
				log.Fatal("unkonw relate table")
			}

			t := field.BelongsTo

			switch relate.Type {
			case "has_one":
				t = field.HasOne
			case "has_many":
				t = field.HasMany
			case "many_to_many":
				t = field.Many2Many
			}

			tmpModel := g.GenerateModel(relate.Table)
			opt := gen.FieldRelate(t, common.From(relate.Table).ToCamel(), tmpModel, &field.RelateConfig{
				GORMTag: field.GormTag{"foreignKey": []string{relate.Column}},
			})
			opts = append(opts, opt)
		}
		if tab.As == "" {
			models[index] = g.GenerateModel(tab.Name, opts...)
		} else {
			models[index] = g.GenerateModelAs(tab.Name, tab.As, opts...)
		}
	}

	if !cfg.OnlyModel {
		g.ApplyBasic(models...)

	}

	g.Execute()
	fmt.Println("Done.")
}

// Conndb 连接数据库
func Conndb(dsnAndType ...string) (*gorm.DB, error) {
	var dsn string
	var dbtype string

	num := len(dsnAndType)
	if num == 0 {
		dsn = os.Getenv("DB_DSN")
	}
	if num > 0 {
		dsn = dsnAndType[0]
	}

	if num > 1 {
		dbtype = dsnAndType[1]
	}
	if dsn == "" {
		return nil, errors.New("**error** DB_DSN is empty")
	}
	switch dbtype {
	case "postgres":
		return gorm.Open(postgres.Open(dsn), &gorm.Config{})
	case "sqlite":
		return gorm.Open(sqlite.Open(dsn), &gorm.Config{})
	case "sqlserver":
		return gorm.Open(sqlserver.Open(dsn), &gorm.Config{})
	case "clickhouse":
		return gorm.Open(clickhouse.Open(dsn), &gorm.Config{})
	default:
		return gorm.Open(mysql.Open(dsn), &gorm.Config{})
	}
}
